#include <iostream>
#include <sstream>
#include <string>
#include <assert.h>
#include "status.hxx"
#include "log/logfile.hxx"
#include "util/fs.hxx"
#include "util/crc32.hxx"
#include "dbt.hxx"

std::string path;
uint32_t id = 1;

LogFileWriter* lfw;
LogFileReader* lfr;

void init() {
    
    DbStatus ret;

    lfw = new LogFileWriter(id, path, 50*1024*1024);
    ret = lfw->init();
    if(!ret.isok()) {std::cout<<ret.msg()<<"\n";return;}

    lfr = new LogFileReader(id, path);
    ret = lfr->init();
    if(!ret.isok()) {std::cout<<ret.msg()<<"\n";return;}    
}

void clean() {
    
    DbStatus ret;

    std::ostringstream oss; oss<<path<<"/"<<id<<".log";
    ret = FsUtility::deleteFile(oss.str());
    if(!ret.isok()) {std::cout<<"clean logs: "<<ret.msg()<<"\n";return;}

    delete lfw; delete lfr;
    lfw = NULL; lfr = NULL;
}

//truncate back to n bytes
DbStatus truncatelog(int n) {
    
    uint64_t fsize;
    std::ostringstream oss; oss<<path<<"/"<<id<<".log";
    
    DbStatus ret;
    ret = FsUtility::getFileSize(oss.str(), fsize);
    if(!ret.isok()) return ret;

    ret = FsUtility::truncateFile(oss.str(), fsize - n);
    return ret;
}


LogRecord* constructRecord(uint64_t addrOld,
                          uint64_t addrNew,
                          const std::string& storeName, 
                          const std::string& partName, 
                          uint32_t loadSizeOld, 
                          uint32_t loadSizeNew, 
                          const std::string& data) {
    
    LogRecord* rec = new LogRecord;
    rec->setAddrNew(addrNew);
    rec->setAddrOld(addrOld);
    rec->setDsName(storeName);
    rec->setPartitionName(partName);
    rec->setLoadSizeOld(loadSizeOld);
    rec->setLoadSizeNew(loadSizeNew);
    Dbt dbt; char* buf = new char[data.size()];
    memcpy(buf, data.data(), data.size());
    dbt.setBuffer(buf, data.size(), data.size(), DBT_MALLOC);
    rec->setDataNew(dbt);

    return rec;
}
    
void testassert(LogRecord& rec1, LogRecord& rec2) {
    
    assert(rec1.getAddrNew() == rec2.getAddrNew());
    assert(rec1.getAddrOld() == rec2.getAddrOld());
    assert(rec1.getDsName() == rec2.getDsName());
    assert(rec1.getPartitionName() == rec2.getPartitionName());
    assert(rec1.getLoadSizeOld() == rec2.getLoadSizeOld());
    assert(rec1.getLoadSizeNew() == rec2.getLoadSizeNew());
    assert(rec1.getDataNew() == rec2.getDataNew());
}

int testEmpty() {
    
    DbStatus ret;

    init();    

    bool found; LogRecord rec;
    ret = lfr->getNextLogRecord(found, rec);
    assert(found == false);
    if(!ret.isok()) {std::cout<<ret.msg()<<"\n";return -1;}

    clean();

    std::cout<<"testEmpty pass\n";
    return 0;
}
   
//last record is partial
int testPartial() {
    
    DbStatus ret;

    init();

    //write record
    LogRecord* rec = constructRecord(0x0000123400560078, 0x0000342100650087, "datastore", "partitionname", 10, 1000, "123456789");
    Dbt* dbt = rec->serialize();
    ret = lfw->append(dbt);
    if(!ret.isok()) {std::cout<<ret.msg()<<"\n";return -1;}
    ret = lfw->flush();

    lfw->close();
    
    truncatelog(10);
    
    bool found; LogRecord recread;
    ret = lfr->getNextLogRecord(found, recread);
    if(!ret.isok() && DbStatus::DB_CORRUPTION != ret.getCode()) {std::cout<<ret.msg()<<"\n";return -1;}

    assert(DbStatus::DB_CORRUPTION == ret.getCode());
    assert(found == false); 

    clean();

    std::cout<<"testPartial pass\n";
    return 0;
}

//a middle record has wrong checksum 
int testMiddleCorruption() {
    return 0;
}

//a tail record has wrong checksum
int testTailCorruption() {
    return 0;
}

//small record
int testSmallWriteRead() {
    
    DbStatus ret;

    init();

    std::vector<LogRecord*> v;
    for(int i = 0; i < 1000; ++i) {
        LogRecord* rec = constructRecord(i, 2*i, "datastore", "partitionname", 10, 1000, "123456789");
        v.push_back(rec);
        
        Dbt* dbt = rec->serialize();
        ret = lfw->append(dbt);

        if(!ret.isok()) {std::cout<<ret.msg()<<"\n";return -1;}
    }
    ret = lfw->flush();

    for(int i = 0; i < 1000; ++i) {
        bool found; LogRecord recread;
        ret = lfr->getNextLogRecord(found, recread);
        if(!ret.isok()) {std::cout<<ret.msg()<<"\n";return -1;}

        assert(found == true);
        testassert(*v[i], recread);
    }

    clean();
    std::cout<<"testSmallWriteRead pass\n";
    return 0;
}

//big record
int testBigWriteRead() {
    
    DbStatus ret;

    init();

    std::string data(60000, 'y');

    std::vector<LogRecord*> v;
    for(int i = 0; i < 500; ++i) {
        LogRecord* rec = constructRecord(i, 2*i, "datastore",
                "partitionname", 10, 1000, data);
        v.push_back(rec);

        Dbt* dbt = rec->serialize();
        ret = lfw->append(dbt);

        if(!ret.isok()) {std::cout<<ret.msg()<<"\n";return -1;}
    }
    ret = lfw->flush();

    for(int i = 0; i < 500; ++i) {
        bool found; LogRecord recread;
        ret = lfr->getNextLogRecord(found, recread);
        if(!ret.isok()) {std::cout<<ret.msg()<<"\n";return -1;}

        assert(found == true);
        testassert(*v[i], recread);
    }

    clean();
    std::cout<<"testBigWriteRead pass\n";
    return 0;
}

void runAllTests() {
    
    int ret = 0;
    ret = testEmpty(); 
    if(ret != 0) std::cout<<"testEmpty failed\n";

    ret = testPartial();
    if(ret != 0) std::cout<<"testPartial failed\n";

    //testMiddleCorruption();
    //testTailCorruption();

    ret = testSmallWriteRead();
    if(ret != 0) std::cout<<"testSmallWriteRead failed\n";
    ret = testBigWriteRead();
    if(ret != 0) if(ret != 0) std::cout<<"testBigWriteRead failed\n";
}

int main(int argc, char** argv) {
    
    CRC32::init();
    path = std::string(argv[1]);
    runAllTests();
}

